/*
 * File     : BufferStructureField.java
 * Created  : 20 Apr 2011
 * Revision : $Revision$
 *
 * Copyright © 2006-9 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.structure;

import static com.googlecode.dni.internal.DniTypes.POINTER_TYPE;
import static com.googlecode.dni.internal.DniTypes.STRUCTURE_HELPER_TYPE;
import static org.objectweb.asm.Opcodes.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import com.googlecode.dni.type.nativetype.NativeTypes;
import com.googlecode.dni.type.nativetype.SizeT;
import com.googlecode.dni.type.nativetype.Unsigned;
import com.googlecode.dni.type.structure.IllegalStructureException;


/**
 * Encapsulates details of a pointer to primitive structure's field used during
 * class generation.
 *
 * @author Matthew Wilson
 */
final class BufferStructureField extends StructureField
{

    /** Primitive type for a pointer; assumes sizeof(size_t) == sizeof(void*) */
    private static final PrimitiveType POINTER = PrimitiveType.getType( SizeT.class );

    /** These annotations make no sense. */
    private static final List< Class< ? extends Annotation > > FORBIDDED_ANNOTATION_TYPES =
        new ArrayList< Class< ? extends Annotation > >();

    static
    {
        FORBIDDED_ANNOTATION_TYPES.add( Unsigned.class );
        FORBIDDED_ANNOTATION_TYPES.addAll( NATIVE_SIZED_TYPES );
    }

    private Class< ? > elementType;

    private Class< ? extends Buffer > bufferType;

    private PrimitiveType primitiveType;


    /**
     * {@inheritDoc}
     */
    @Override
    Class< ? > getFieldType()
    {
        return this.bufferType;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void setGetter( final Method getter )
    {
        super.setGetter( getter );

        this.bufferType = getter.getParameterTypes()[ 0 ].asSubclass( Buffer.class );

        checkAnnotations( FORBIDDED_ANNOTATION_TYPES );

        if ( this.elementType == null )
        {
            // TODO replace with a Map?
            if ( this.bufferType == ByteBuffer.class )
            {
                this.elementType = byte.class;
            }
            else if ( this.bufferType == ShortBuffer.class )
            {
                this.elementType = short.class;
            }
            else if ( this.bufferType == CharBuffer.class )
            {
                this.elementType = char.class;
            }
            else if ( this.bufferType == IntBuffer.class )
            {
                this.elementType = int.class;
            }
            else if ( this.bufferType == LongBuffer.class )
            {
                this.elementType = long.class;
            }
            else if ( this.bufferType == FloatBuffer.class )
            {
                this.elementType = float.class;
            }
            else if ( this.bufferType == DoubleBuffer.class )
            {
                this.elementType = double.class;
            }
            else
            {
                throw new IllegalStructureException( "Unsupported Buffer type: " + getter );
            }
        }

        this.primitiveType = PrimitiveType.getType( this.elementType );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void checkSetter( final Method method )
    {
        java.lang.reflect.Type[] parameterTypes = method.getGenericParameterTypes();
        if ( parameterTypes.length != 1 )
        {
            throw new IllegalArgumentException( "Structure setter must take one parameter: " + method );
        }

        if ( !this.bufferType.equals( parameterTypes[ 0 ] ) )
        {
            throw new IllegalArgumentException( "Structure setter / getter types do not agree: " + method );
        }
    }

    @Override
    String getCType()
    {
        return this.primitiveType.getCType() + "*";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getSize()
    {
        // size in the structure is that of a pointer
        return NativeTypes.POINTER_SIZE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetter( final MethodVisitor methodVisitor,
                      final String implName )
    {
        methodVisitor.visitVarInsn( ALOAD, 1 );

        writeGetPointer( methodVisitor, implName );
        POINTER.writeGet( methodVisitor, true );

        methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                       STRUCTURE_HELPER_TYPE,
                                       "updateBuffer",
                                       "(" + Type.getDescriptor( this.bufferType ) + "J)Z" );

        methodVisitor.visitInsn( Opcodes.IRETURN );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeSetter( final MethodVisitor methodVisitor,
                      final String implName )
    {
        writeGetPointer( methodVisitor, implName );

        methodVisitor.visitVarInsn( ALOAD, 1 );
        methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                       POINTER_TYPE,
                                       "fromBuffer",
                                       "(Ljava/nio/Buffer;)L" + POINTER_TYPE + ";" );
        methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
                                       POINTER_TYPE,
                                       "getAddress",
                                       "()J" );

        POINTER.writeSet( methodVisitor );

        methodVisitor.visitInsn( Opcodes.RETURN );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeEqualsFragment( final MethodVisitor methodVisitor,
                              final Label returnFalseLabel,
                              final String implName )
    {
        // watch out; this is really just a pointer!
        // (signed comparison is fine; we're just testing equal values, so don't
        // care how they are promoted)

        // TODO could be very slightly more efficient on 32-bit by only comparing int
        // BUT I DON'T CARE

        writeGetPointer( methodVisitor, implName );
        POINTER.writeGet( methodVisitor, false );

        methodVisitor.visitVarInsn( ALOAD, 2 );
        writeGetPointerFrom( methodVisitor, implName );
        POINTER.writeGet( methodVisitor, false );

        methodVisitor.visitInsn( LCMP );
        methodVisitor.visitJumpInsn( IFNE, returnFalseLabel );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeHashCodeFragment( final MethodVisitor methodVisitor, final String implName )
    {
        writeGetPointer( methodVisitor, implName );
        POINTER.writeGet( methodVisitor, true );
        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       STRUCTURE_HELPER_TYPE,
                                       "hashCode",
                                       "(J)I" );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeToStringFragment( final MethodVisitor methodVisitor, final String implName )
    {
        methodVisitor.visitLdcInsn( "0x" );
        methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                       "java/lang/StringBuilder",
                                       "append",
                                       "(Ljava/lang/String;)Ljava/lang/StringBuilder;" );

        writeGetPointer( methodVisitor, implName );
        POINTER.writeGet( methodVisitor, true );
        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       "java/lang/Long",
                                       "toHexString",
                                       "(J)Ljava/lang/String;" );

        methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                       "java/lang/StringBuilder",
                                       "append",
                                       "(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
    }

}
